package lumis.content.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import lumis.portal.PortalException;
import lumis.portal.PortalObjectAlreadyExistsException;
import lumis.portal.event.persistence.PortalEventEntityListener;
import lumis.portal.stability.StableMinor;
import lumis.util.LocaleUtil;

import org.hibernate.Hibernate;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;

/**
 * A content entity object. A content may have its data specified in multiple locales.
 * 
 * @stereotype entity
 * @composed 1 - * ContentLocale
 * 
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 * @since 4.2.0
 */
@StableMinor(version = "6.0", sinceVersion = "4.2")
@Entity
@Table(name="lum_Content")
@GenericGenerator(name="portal-guid", strategy="lumis.portal.dao.hibernate.GuidGenerator")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@EntityListeners(value=PortalEventEntityListener.class)
@SuppressWarnings("serial")
public class Content implements Serializable
{
	@Id @GeneratedValue(generator="portal-guid")
	private String id;
	
	private String serviceInstanceId;
	
	private String sourceId;
	
	private String defaultLocale;
	
	@OneToMany(mappedBy="content", cascade=CascadeType.ALL)
	@MapKey(name="locale")
	@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Map<String, ContentLocale> contentLocales = new HashMap<String, ContentLocale>();
	
	@SuppressWarnings("unused")
	@OneToMany(mappedBy="associatedContent", cascade=CascadeType.ALL)
	@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Collection<ContentAssociation> contentAssociations = new ArrayList<ContentAssociation>();
	
	// -- Property Methods -- //
	
	/**
	 * Returns this content's identifier.
	 * @return this content's identifier.
	 * @since 4.2.0
	 */
	public String getId()
	{
		return id;
	}
	
	/**
	 * Returns the identifier of the service instance this content belongs to.
	 * @return the identifier of the service instance this content belongs to.
	 * @since 4.2.0
	 */
	public String getServiceInstanceId()
	{
		return serviceInstanceId;
	}
	
	/**
	 * Sets the service instance this content belongs to.
	 * @param serviceInstanceId the service instance identifier.
	 * @since 4.2.0
	 */
	public void setServiceInstanceId(String serviceInstanceId)
	{
		this.serviceInstanceId = serviceInstanceId;
	}
	
	/**
	 * Returns the source this content belongs to.
	 * @param sourceId the source identifier.
	 * @since 4.2.0
	 */
	public String getSourceId()
	{
		return sourceId;
	}
	
	/**
	 * Sets the source this content belongs to.
	 * @param sourceId the source identifier.
	 * @since 4.2.0
	 */
	public void setSourceId(String sourceId)
	{
		this.sourceId = sourceId;
	}
	
	/**
	 * Returns the default locale for this content.
	 * @return the default locale for this content, or <code>null</code> if
	 * this content has no default locale.
	 * @since 4.2.0
	 */
	public Locale getDefaultLocale()
	{
		if (defaultLocale == null)
			return null;
		
		try
		{
			return LocaleUtil.FromString(defaultLocale);
		}
		catch (PortalException e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Sets the default locale for this content.
	 * @param defaultLocale the locale to set. May be <code>null</code> to set
	 * that this content has no default locale.
	 * @since 4.2.0
	 */
	public void setDefaultLocale(Locale defaultLocale)
	{
		this.defaultLocale = defaultLocale != null ? defaultLocale.toString() : null;
	}

	/**
	 * Returns the locale specific objects of this content.
	 * @return the locale specific objects of this content.
	 * @since 4.2.0
	 */
	public Collection<ContentLocale> getContentLocales()
	{
		return Collections.unmodifiableCollection(contentLocales.values());
	}
	
	// -- Operations -- //
	
	/**
	 * Adds a new locale specific content object in this content.
	 * @param locale the locale.
	 * @return the locale specific content object created.
	 * @throws PortalObjectAlreadyExistsException if this content already 
	 * contains an object for the given locale.
	 * @since 4.2.0
	 */
	public ContentLocale addContentLocale(Locale locale) throws PortalException
	{
		if (locale == null)
			throw new IllegalArgumentException("locale cannot be null");
		
		String localeString = locale.toString();
		if (contentLocales.containsKey(localeString))
			throw new PortalObjectAlreadyExistsException("Locale " + locale + " already exists in this content");
		
		ContentLocale contentLocale = new ContentLocale(this);
		contentLocale.setCreatedBy(ContentCoreUtil.getCurrentUserId());
		contentLocale.setCreatedDateTime(new Date());
		contentLocale.setLocaleString(localeString);
		contentLocales.put(localeString, contentLocale);
		return contentLocale;
	}
	
	/**
	 * Returns the locale specific content object, for the given locale, 
	 * optionally using the default locale.
	 * @param locale the locale.
	 * @param fallbackToDefaultLocale if true, the default locale for this content
	 * will be used if no contentLocale for the specified locale exists.
	 * @return the locale specific content object, or <code>null</code> if 
	 * no object exists, according to the given parameters.
	 * @since 4.2.0
	 */
	public ContentLocale getContentLocale(Locale locale, boolean fallbackToDefaultLocale)
	{
		ContentLocale result = null;
		if (locale != null)
			result = contentLocales.get(locale.toString());
		
		if (result == null && fallbackToDefaultLocale && defaultLocale != null)
			result = contentLocales.get(defaultLocale);
		
		return result;
	}
	
	/**
	 * Adds a contentLocale in the internal collection of this content.
	 * @param contentLocale the contentLocale.
	 * @since 4.2.0
	 */
	void addContentLocale(ContentLocale contentLocale)
	{
		contentLocales.put(contentLocale.getLocaleString(), contentLocale);
	}
	
	/**
	 * Removes a contentLocale from the internal collection of this content.
	 * @param contentLocale the contentLocale.
	 * @since 4.2.0
	 */
	void removeContentLocale(ContentLocale contentLocale)
	{
		// must initialize contentLocales, because the remove method on proxy
		// collection was not actually removing the object
		Hibernate.initialize(contentLocales);
		contentLocales.remove(contentLocale.getLocaleString());
	}
}